using System;
using System.Diagnostics;

namespace OpenRay
{
	//This class is immutable
	public struct Vector
	{
		public readonly float X, Y, Z;
		private const double Delta = 0.00005;
		
		public Vector(float x, float y, float z)
		{
			X = x;
			Y = y;
			Z = z;
		}
		
		public bool Equals (Vector v)
		{			
			return Math.Abs( X - v.X ) < Delta &&
				   Math.Abs( Y - v.Y ) < Delta &&
				   Math.Abs( Z - v.Z ) < Delta;
		}
		
		public override int GetHashCode()
		{
			return (int)X + (int)Y + (int)Z;
		}
		
		public override string ToString()
		{			
			return X.ToString() + ", " + Y.ToString() + ", " + Z.ToString();
		}
		
		public float Length()
		{
			return (float)Math.Sqrt((double)Vector.DotProduct(this, this));
		}
		
		public static Vector operator * (Vector v, float f) 
		{
			return new Vector( v.X * f, v.Y * f, v.Z * f );
		}
		
		public static Vector operator * (float f, Vector v) 
		{
			return v * f;
		}
		
		public static Vector operator + (Vector v1, Vector v2)
		{
			return new Vector( v1.X + v2.X,
			                   v1.Y + v2.Y,
			                   v1.Z + v2.Z );
		}
		
		public static Vector operator - (Vector v1, Vector v2)
		{
			return new Vector( v1.X - v2.X,
			                   v1.Y - v2.Y,
			                   v1.Z - v2.Z );
		}
		
		public static Vector operator - (Vector v)
		{
			return new Vector( -v.X, -v.Y, -v.Z );
		}
		
		public bool IsNormal()
		{
			float f = X * X +
				      Y * Y +
				      Z * Z;
			
			return Math.Abs( 1.0f - f ) < Delta; 
		}
		
		public static Vector Normalize( Vector v )
		{
			float invLength = 1f / v.Length();
			return v * invLength;
		}
		
		public static float DotProduct(Vector v1, Vector v2)
		{
			return	v1.X * v2.X + 
				    v1.Y * v2.Y +
					v1.Z * v2.Z;
		}
		
		public static Vector CrossProduct(Vector v1, Vector v2)
		{
			return new Vector( v1.Y * v2.Z - v1.Z * v2.Y,
			                   v1.Z * v2.X - v1.X * v2.Z,
			                   v1.X * v2.Y - v1.Y * v2.X );
		}
		
		public static Vector HalfAngle(Vector v1, Vector v2)
		{
			Debug.Assert( v1.IsNormal(), "Vector.HalfAngle: Vector 1 is not normalized" );
			Debug.Assert( v2.IsNormal(), "Vector.HalfAngle: Vector 2 is not normalized" );
			
			return Vector.Normalize( v1 + v2 );
		}
	
		public static Vector Reflect(Vector incident, Vector normal)
		{
			Debug.Assert( incident.IsNormal(), "Vector.Reflect: Incident vector is not normalized" );
			Debug.Assert( normal.IsNormal(), "Vector.Reflect: Normal vector is not normalized" );
			
			return Vector.Normalize( incident - 2 * Vector.DotProduct(normal,incident) * normal );
		}
		
		public static Vector Refract(Vector incident, Vector normal, float nQuotient)
		{
			Debug.Assert( incident.IsNormal(), "Vector.Refract: Incident vector is not normalized" );
			Debug.Assert( normal.IsNormal(), "Vector.Refract: Normal vector is not normalized" );
			Debug.Assert( nQuotient >= 0, "Vector.Refract: nQuotient is less than 0" );
			
			float cosI = Vector.DotProduct(normal, incident);
			float sinT2 = nQuotient * nQuotient * ( 1f - cosI * cosI );
			
			return ( sinT2 > 1f ) ? new Vector( 0, 0, 0 ) :
									nQuotient * incident - ( nQuotient + (float)Math.Sqrt(1.0 - (double)sinT2) ) * normal;
		}
	}
}

